#!/usr/bin/env python

import sys
import re
import struct
import binascii
from base64 import b64encode

opcodes = {
    'NO_OP':      0,
    'IMPORT':     5,
    'SYS_CALL':     6,
    'BREAKPOINT':     7,
    'PRINT':     8,
    'PUSH_CLASS':     10,
    'PUSH_FUNCTION':     11,
    'PUSH_PROPERTY':     12,
    'PUSH_VAR':     13,
    'PUSH_CONST':     14,
    'POP':     20,
    'DUPLICATE':     25,
    'SWAP':     26,
    'PUSH_ITER':     30,
    'POP_ITER':     31,
    'NEXT_ITER':     32,
    'ITERATE':     33,
    'IS_A':     35,
    'PUSH_LOCALS':     36,
    'POP_LOCALS':     37,
    'JUMP_TO_OBJECT':     40,
    'JUMP_IF_TRUE':     41,
    'JUMP_IF_FALSE':     42,
    'JUMP':     43,
    'CALL':     45,
    'CALL_MEMBER':     46,
    'RETURN':     47,
    'CALL_RETURN':     48,
    'MAKE_LIST':     50,
    'MAKE_MAP':     51,
    'CONCATENATE':     55,
    'SPLIT':     56,
    'JOIN':     57,
    'LENGTH':     58,
    'ADD':     60,
    'SUBTRACT':     61,
    'MULTIPLY':     62,
    'DIVIDE':     63,
    'MODULUS':     64,
    'EXPONENT':     65,
    'BIT_AND':     66,
    'BIT_OR':     67,
    'BIT_XOR':     68,
    'PUSH_CODE_OBJ':     70,
    'PUSH_PC_OFFSET':     71,
    'PUSH_STACK_LENGTH':     72,
    'STR_CMP':     75,
    'ORDINAL':     76,
    'CHARACTER':     77,
    'EQUALS':     80,
    'GREATER_THAN':     81,
    'LESS_THAN':     82,
    'GREATER_EQUAL':     83,
    'LESS_EQUAL':     84,
    'GET_LIST_ITEM':     85,
    'SET_LIST_ITEM':     86,
    'GET_LIST_SLICE':     87,
    'GET_MAP_ITEM':     88,
    'SET_MAP_ITEM':     89,
    'GET_STRING_ITEM':     90,
    'SET_STRING_ITEM':     91,
    'GET_STRING_SLICE':     92,
    'ALLOC':     95,
    'GET_ID':     96,
    'ID_EQUALS':     97,
    'HAS_MEMBER':     100,
    'GET_MEMBER':     101,
    'SET_MEMBER':     102,
    'LIST_APPEND':     105,
    'LIST_EXTEND':     106,
    'LIST_REMOVE':     107,
    'FORMAT_INT':     110,
    'FORMAT_FLOAT':     111,
    'ASSERT':     115,
    'STOP':     127,
    '_ASSERT1':     120,
    '_ASSERT2':     121,
    '_STACK_CHECK':     122,
 }


class KalObject(object):
    def __init__(self,*vargs):
        self.ops = vargs
        
#type: (op_code,format_specifier,intern)
type_specs = {
    int:200,
    float:201,
    str:202,
    unicode:202,
    bool:203,
    list:204,
    tuple:204,
    dict:205,
    KalObject:206,
    'KalFile':207,
}



def append_arg(arg,compiled,interned_strings):
    compiled.append(type_specs[type(arg)])
    if type(arg) == int:
        compiled.extend(struct.pack('i',arg))
    elif type(arg) == float:
        compiled.extend(struct.pack('d',arg))
    elif type(arg) == bool:
        if arg:
            compiled.append(1)
        else:
            compiled.append(0)
    elif type(arg) in (str,unicode):
        compiled.extend(struct.pack('I',len(interned_strings)))
        interned_strings.append(unicode(arg))
    elif type(arg) in (list,tuple):
        compiled.extend(struct.pack('I',len(arg)))
        for item in arg:
            append_arg(item,compiled,interned_strings)
    elif type(arg) == dict:
        compiled.extend(struct.pack('I',len(arg)))
        for k,v in arg.iteritems():
            append_arg(k,compiled,interned_strings)
            append_arg(v,compiled,interned_strings)
    elif type(arg) == KalObject:
        o = compile_object(arg.ops,interned_strings)
        compiled.extend(struct.pack('I',len(o)))
        compiled.extend(o)

        
def compile_object(obj,interned_strings):
    compiled = bytearray()
    for line in obj:
        if line == []: continue
        compiled.append(line[0])
        for arg in line[1:]:
            append_arg(arg,compiled,interned_strings)
    return compiled

def compile_file(in_file, name):
    in_file = re.sub(r'#.*\n', '\n', in_file)
    assembly = in_file.replace('OBJECT:\n','KalObject([')
    assembly = assembly.replace('END OBJECT','])')
    assembly = '[' + assembly.replace('\n','],[') + ']'

    try:
        staged_code = eval(assembly,opcodes,{'KalObject':KalObject})
    except Exception, e:
        print "Error in " + name + " - " + str(e)
        print assembly
        return

    compiled = bytearray()
    interned_strings = []
    with open('.'.join(arg.split('.')[:-1] + ['kalc']),'wb') as f:
        out = bytearray()
        code = compile_object(staged_code,interned_strings)
        
        out += bytearray([type_specs['KalFile']])
        out += struct.pack('I',len(interned_strings))
        for string in interned_strings:
            b = bytearray(string,'utf-8')
            out += struct.pack('I',len(b))
            out += b
        out += struct.pack('I',len(code))
        out += code
        f.write('"')
        f.write(b64encode(out))
        f.write('"')
       
if __name__ == '__main__':
    for arg in sys.argv[1:]:
        with open(arg) as f:
            compile_file(f.read(), arg)